package model.locations;
import java.util.ArrayList;
import java.util.List;

import exceptions.InvalidDirectionException;
import exceptions.LockedDoorException;
import exceptions.NoDoorException;
import exceptions.OutOfBoundsException;
import exceptions.ThingNotInArea;

import model.boundary.Boundary;
import model.boundary.Door;
import model.boundary.ExitBoundary;
import model.boundary.PassThroughBoundary;
import model.boundary.WallBoundary;
import model.locations.Area;
import model.locations.NoGoArea;
import model.locations.Path;
import model.things.Enemy;
import model.things.Thing;
import model.things.You;


public class Level {
	private Area[][] setting;
	// the verticalBoundary array specifies boundary conditions for areas linked vertically
	// if there is no area above or below a certain area then the boundary should Boundary
	//should automatically be a WallBoundary (to prevent going off map. this can later be
	//changed to a exit/entry
	private Boundary[][] verticalBoundary;
	// the horizontalBoundary array specifies boundary conditions for areas linked horizontally
	// if there is no area to the left or right of a certain area then the boundary should
	//automatically be a WallBoundary
	private Boundary[][] horizontalBoundary;
	
	private int size;
	private int xStart;
	private int yStart;
	
	You player;
	Enemy enemy;
	private int previousX,previousY;
	private int currentX,currentY;
	private List<Thing> thingsInCurrentArea=new ArrayList<Thing>();
	private List<Thing> thingsInPreviousArea=new ArrayList<Thing>();
	private String additionalDescription="";
	private String directionOfDoor;

	
	public Level(int size,char exitDirection,int xExit,int yExit,int xStart,int yStart,You player) throws OutOfBoundsException, InvalidDirectionException{
		
		this.player=player;
		//defensive programming
		if(xExit>size||yExit>size||xStart>size||yStart>size)
		throw new OutOfBoundsException();
		
		if(!(exitDirection=='s'||exitDirection=='e'||exitDirection=='n'||exitDirection=='w'))
		throw new InvalidDirectionException();
		
		this.size=size;
		this.xStart=xStart;
		this.yStart=yStart;
		
		setting =new Area[size][size];
		verticalBoundary = new Boundary[size+1][size];
		horizontalBoundary =new Boundary[size][size+1];
		
		//this will make all areas into paths
		for(int x=0;x<size;x++){
			for(int y=0;y<size;y++){
				setting[x][y]=new Path();
			}	
		}
		
		//this will set up the boundaries initially to be able to allow players to pass though
		for(int count=0;count<setting.length;count++){
			for(int count2=0;count2<setting.length+1;count2++){
				verticalBoundary[count2][count]=new PassThroughBoundary();
				horizontalBoundary[count][count2]=new PassThroughBoundary();
			}
		}
		//this will set the edge of the map to have WallBoundaries 
		for(int count=0;count<setting.length;count++){
			int count2=0;
			verticalBoundary[count2][count]=new WallBoundary();
			horizontalBoundary[count][count2]=new WallBoundary();
		}
		for(int count=0;count<setting.length;count++){
			int count2=setting.length;
			verticalBoundary[count2][count]=new WallBoundary();
			horizontalBoundary[count][count2]=new WallBoundary();
		}			
		
		//creates exit
		if(exitDirection=='n')
			verticalBoundary[xExit][yExit]=new ExitBoundary();
		if(exitDirection=='s')
			verticalBoundary[xExit+1][yExit]=new ExitBoundary();
		if(exitDirection=='e')
			horizontalBoundary[xExit][yExit+1]=new ExitBoundary();
		if(exitDirection=='w')
			horizontalBoundary[xExit][yExit]=new ExitBoundary();
		
		
	}
	
	public int getSize(){
		return this.size;
	}
	
	public int getStartX(){
		return this.xStart;
	}
	public int getStartY(){
		return this.yStart;
	}	

	public void addAreaDescription(String description,int x,int y){
		setting[x][y].addDescription(description);
	}
	
	public void addBoundaryDescription(String description,int x,int y,boolean vertical){
		if(vertical)
		verticalBoundary[x][y].addDescription(description);
		else horizontalBoundary[x][y].addDescription(description);
		
	}
	
	public void addThing(Thing thing) throws OutOfBoundsException{
		if(thing.getX()>this.size||thing.getY()>this.size)
		throw new OutOfBoundsException();
		setting[thing.getX()][thing.getY()].addThing(thing);
	}
	
	public void removeThing(String thing,int x,int y){
		setting[x][y].removeThing(thing);
	}
	
	public void addNoGoArea(int x,int y){
		setting[x][y]=new NoGoArea();
	}
	
	public void addBoundary(int x,int y,boolean vertical,boolean door) throws OutOfBoundsException{
		
		if(!door){
			if(vertical){
				if(x>this.size+1||y>this.size)
					throw new OutOfBoundsException();
				verticalBoundary[x][y]=new WallBoundary();
			}
			else{
				if(x>this.size||y>this.size+1)
					throw new OutOfBoundsException();
				horizontalBoundary[x][y]=new WallBoundary();
			}
		}
		else{
			if(vertical){
				if(x>this.size+1||y>this.size)
					throw new OutOfBoundsException();
				verticalBoundary[x][y]=new Door(false);
			}
			else{
				if(x>this.size||y>this.size+1)
					throw new OutOfBoundsException();
				horizontalBoundary[x][y]=new Door(false);
			}
		}
	}

	public String getStatusBoundary(int x,int y,boolean vertical) throws OutOfBoundsException{
	
	if(vertical){
		if(x>this.size+1||y>this.size)
			throw new OutOfBoundsException();
		return verticalBoundary[x][y].getStatus();
	}
	else{
		if(x>this.size-1||y>this.size)
			throw new OutOfBoundsException();
		return horizontalBoundary[x][y].getStatus();
	}
	}
	
	public String getDescriptionBoundary(int x,int y,boolean vertical) throws OutOfBoundsException{
	
	if(!vertical){
		if(x>this.size-1||y>this.size+1)
			throw new OutOfBoundsException();
		return horizontalBoundary[x][y].getDescription();
		}
	else{
		if(x>this.size+1||y>this.size)
			throw new OutOfBoundsException();
		return verticalBoundary[x][y].getDescription();
	}
	}
	
		
	public String getTypeArea(int x,int y) throws OutOfBoundsException{
		if(x>this.size||y>this.size)
			throw new OutOfBoundsException();
		return setting[x][y].getType();
	}
	
	public String getDescriptionArea(int x,int y) throws OutOfBoundsException{
		if(x>this.size||y>this.size)
			throw new OutOfBoundsException();
		return setting[x][y].getDescription()+additionalDescription;
	}
	
	public List<Thing> getThingsInVicinity(int x,int y) throws ThingNotInArea, OutOfBoundsException{
		if(x>this.size||y>this.size)
			throw new OutOfBoundsException();
		return setting[x][y].getThingsInVicinity();
	}
	//this method updates the global list describing whats around the player
	//only called when the player moves
	public void update(){
		previousX=currentX;
		previousX=currentX;
		currentX=player.getX();
		currentY=player.getY();
		
		try {
			thingsInPreviousArea=setting[previousX][previousY].getThingsInVicinity();
			thingsInCurrentArea=setting[currentX][currentY].getThingsInVicinity();
		} catch (ThingNotInArea e) {}
		
		directionOfDoor="none";
		if(verticalBoundary[currentX][currentY].getType().equals("DOOR"))
			directionOfDoor="north";
		if(verticalBoundary[currentX+1][currentY].getType().equals("DOOR"))
			directionOfDoor= "south";
		if(horizontalBoundary[currentX][currentY].getType().equals("DOOR"))
			directionOfDoor="west";
		if(horizontalBoundary[currentX][currentY+1].getType().equals("DOOR"))
			directionOfDoor="east";
		if(!(directionOfDoor=="none"))
			additionalDescription+="\nThere is a door leading to the "+directionOfDoor;
		
		if(!(currentX==0)){	
			if(setting[currentX-1][currentY].findTypeInVicinity("ENEMY"))
				additionalDescription+="\nBeware! there is an enemy to the north";}
		if(!(currentX==size-1)){
			if(setting[currentX+1][currentY].findTypeInVicinity("ENEMY"))
				additionalDescription+="\nBeware! there is an enemy to the south";}
		if(!(currentY==0)){
			if(setting[currentX][currentY-1].findTypeInVicinity("ENEMY"))
				additionalDescription+="\nBeware! there is an enemy to the west";}
		if(!(currentY==size-1))	
			if(setting[currentX][currentY+1].findTypeInVicinity("ENEMY"))
				additionalDescription+="\nBeware! there is an enemy to the east";	
	}
	
	public Thing getThingTypeCurrentArea(String type) throws ThingNotInArea{
		
		for(Thing thing:thingsInCurrentArea){
			if(thing.getType()==type)
			return thing;
		}
		throw new ThingNotInArea(type);
	}
	
	public boolean getThingType(String type,String label){
		
		for(Thing thing:thingsInCurrentArea){
			if(thing.getType()==type&&thing.getLabel()==label)
			return true;
		}
		return false;
	}
	
	public Thing getThingTypePreviousArea(String type) throws ThingNotInArea{
		
		for(Thing thing:thingsInPreviousArea){
			if(thing.getType()==type)
			return thing;
		}
		throw new ThingNotInArea(type);
	}	
	
	public boolean openAndClose(boolean open) throws LockedDoorException, NoDoorException{

		if(directionOfDoor=="north")
			return verticalBoundary[currentX][currentY].openAndClose(open);
		else if(directionOfDoor=="south")
			return verticalBoundary[currentX+1][currentY].openAndClose(open);
		else if(directionOfDoor=="west")
			return horizontalBoundary[currentX][currentY].openAndClose(open);
		else if(directionOfDoor=="east")
			return horizontalBoundary[currentX][currentY+1].openAndClose(open);
	
		throw new NoDoorException();
}

	
	public void placePlayer(Thing player){
		
		setting[xStart][yStart].addThing(player);
	}
}
